/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.meitun.mama.inject;

import android.app.Activity;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public final class Injector {

	static final String TAG = "Injector";

	@SuppressWarnings("unchecked")
	private static final void onSave(Field[] fields, Object obj, Bundle outsave) {
		if (fields == null || fields.length == 0)
			return;
		try {
			for (Field field : fields) {
				if (field.isAnnotationPresent(InjectData.class)) {
					field.setAccessible(true);
					Object value = field.get(obj);
					if (value instanceof String) {
						outsave.putString(field.getName(), (String) value);
					} else if (value instanceof Integer) {
						outsave.putInt(field.getName(), (Integer) value);
					} else if (value instanceof Parcelable) {
						outsave.putParcelable(field.getName(), (Parcelable) value);
					} else if (value instanceof Serializable) {
						outsave.putSerializable(field.getName(), (Serializable)value);
					} else if (value instanceof Float) {
						outsave.putFloat(field.getName(), (Float) value);
					} else if (value instanceof Double) {
						outsave.putDouble(field.getName(), (Double) value);
					} else if (value instanceof String[]) {
						outsave.putStringArray(field.getName(), (String[]) value);
					} else if (value instanceof Boolean) {
						outsave.putBoolean(field.getName(), (Boolean)value);
					} else if (value instanceof ArrayList<?>) {
						ArrayList<?> data = (ArrayList<?>) value;
						if (!data.isEmpty()) {
							Object temp = data.get(0);
							if (temp instanceof String) {
								outsave.putStringArrayList(field.getName(), (ArrayList<String>) value);
							} else if (temp instanceof Parcelable) {
								outsave.putParcelableArrayList(field.getName(), (ArrayList<Parcelable>) value);
							}
						}
					}
					field.setAccessible(false);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static final void save(Object obj, Bundle outsave) {
		onSave(obj.getClass().getDeclaredFields(), obj, outsave);
		Class<? extends Object> _supperclass = obj.getClass().getSuperclass();
		while (_supperclass != null) {
			onSave(_supperclass.getDeclaredFields(), obj, outsave);
			_supperclass = _supperclass.getSuperclass();
		}
	}

	private static final void onRestore(Field[] fields, Object obj, Bundle savedInstanceState) {
		if (fields == null || fields.length == 0)
			return;
		try {
			for (Field field : fields) {
				if (field.isAnnotationPresent(InjectData.class)) {
					Object value = savedInstanceState.get(field.getName());
					field.setAccessible(true);
					field.set(obj, value);
					field.setAccessible(false);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static final void onRestore(Object obj, Bundle savedInstanceState) {
		onRestore(obj.getClass().getDeclaredFields(), obj, savedInstanceState);
		Class<? extends Object> _supperclass = obj.getClass().getSuperclass();
		while (_supperclass != null) {
			onRestore(_supperclass.getDeclaredFields(), obj, savedInstanceState);
			_supperclass = _supperclass.getSuperclass();
		}
	}

	public static final void injectActivity(Activity activity) {
		if (activity == null)
			return;
		Field[] fields = activity.getClass().getDeclaredFields();
		injectActivity(activity, fields);
		Class<? extends Object> _supperclass = activity.getClass().getSuperclass();
		while (_supperclass != null) {
			injectActivity(activity, _supperclass.getDeclaredFields());
			_supperclass = _supperclass.getSuperclass();
		}
	}

	private static final void injectActivity(Activity activity, Field[] fields) {
		if (fields == null || fields.length == 0)
			return;
		try {
			for (Field field : fields) {
				if (field.isAnnotationPresent(InjectView.class)) {
					injectViewByActivity(field, activity);
				} else if (field.isAnnotationPresent(InjectFragment.class)) {
					injectFragmentByActivity(field, activity);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static final void injectFragment(Fragment frgment, View view) {
		if (frgment == null || view == null)
			return;
		Field[] fields = frgment.getClass().getDeclaredFields();
		injectFragment(frgment, view, fields);
		Class<?> _supperclass = frgment.getClass().getSuperclass();
		while (_supperclass != null) {
			injectFragment(frgment, view, _supperclass.getDeclaredFields());
			_supperclass = _supperclass.getSuperclass();
		}
	}

	private static final void injectFragment(Fragment frgment, View view, Field[] fields) {
		if (fields == null || fields.length == 0)
			return;
		try {
			for (Field field : fields) {
				if (field.isAnnotationPresent(InjectView.class)) {
					injectViewByFragment(field, frgment, view);
				} else if (field.isAnnotationPresent(InjectFragment.class)) {
					injectFragmentByFragment(field, frgment);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static final void injectObject(Object obj, View view) {
		if (obj == null || view == null)
			return;
		Field[] fields = obj.getClass().getDeclaredFields();
		if (fields == null || fields.length == 0)
			return;
		injectObject(obj, view, fields);
		Class<?> _supperclass = obj.getClass().getSuperclass();
		while (_supperclass != null) {
			injectObject(obj, view, _supperclass.getDeclaredFields());
			_supperclass = _supperclass.getSuperclass();
		}
	}

	private static final void injectObject(Object obj, View view, Field[] fields) {
		try {
			for (Field field : fields) {
				if (field.isAnnotationPresent(InjectView.class)) {
					injectViewByObject(field, obj, view);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static final void injectViewByActivity(Field field, Activity act) throws IllegalArgumentException, IllegalAccessException {
		InjectView mInjectView = field.getAnnotation(InjectView.class);
		int idValue = mInjectView.value();
		field.setAccessible(true);
		Object injectObject = act.findViewById(idValue);
		if (injectObject == null) {
			return;
		}
		field.set(act, injectObject);
		field.setAccessible(false);
	}

	private static final void injectFragmentByActivity(Field field, Activity act) throws IllegalArgumentException, IllegalAccessException {
		InjectFragment mInjectView = field.getAnnotation(InjectFragment.class);
		int idValue = mInjectView.value();
		field.setAccessible(true);
		Object injectObject = ((FragmentActivity) act).getSupportFragmentManager().findFragmentById(idValue);
		if (injectObject == null) {
			return;
		}
		field.set(act, injectObject);
		field.setAccessible(false);
	}

	private static final void injectViewByFragment(Field field, Fragment frgment, View view) throws IllegalArgumentException, IllegalAccessException {
		InjectView mInjectView = field.getAnnotation(InjectView.class);
		int idValue = mInjectView.value();
		field.setAccessible(true);
		Object injectObject = view.findViewById(idValue);
		if (injectObject == null) {
			return;
		}
		field.set(frgment, injectObject);
		field.setAccessible(false);
	}

	private static final void injectFragmentByFragment(Field field, Fragment frgment) throws IllegalArgumentException, IllegalAccessException {
		InjectFragment mInjectView = field.getAnnotation(InjectFragment.class);
		int idValue = mInjectView.value();
		field.setAccessible(true);
		Object injectObject = frgment.getFragmentManager().findFragmentById(idValue);
		if (injectObject == null) {
			return;
		}
		field.set(frgment, injectObject);
		field.setAccessible(false);
	}

	private static final void injectViewByObject(Field field, Object frgment, View view) throws IllegalArgumentException, IllegalAccessException {
		InjectView mInjectView = field.getAnnotation(InjectView.class);
		int idValue = mInjectView.value();
		field.setAccessible(true);
		Object injectObject = view.findViewById(idValue);
		if (injectObject == null) {
			return;
		}
		field.set(frgment, injectObject);
		field.setAccessible(false);
	}

	//
	public static void bindViewData(Object view, Object data) {
		long time = System.currentTimeMillis();
		try {
			Field[] fields = view.getClass().getDeclaredFields();
			for (Field field : fields) {
				if (field.isAnnotationPresent(InjectViewData.class)) {
					InjectViewData injectViewData = field.getAnnotation(InjectViewData.class);
					if (TextUtils.isEmpty(injectViewData.value()))continue;
					field.setAccessible(true);
					setMethodVlaue(field.get(view), getMethodValue(data, injectViewData.value(), injectViewData.getDataArgs(), injectViewData.classArray()));
					field.setAccessible(false);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		Log.e("bindViewData", "Time:" + (System.currentTimeMillis() - time));
	}

	private final static void setMethodVlaue(Object data, Object value) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
		if (data instanceof TextView) {
			((TextView)data).setText((String)value);
		}
	}

	private final static Object getMethodValue(Object data, String methodName, Object[] args, Class<?>... classArray) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
		Method method = data.getClass().getMethod(methodName, classArray);
		if (null == method) return null;
		return method.invoke(data, args);
	}

}
